﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace ParallelRuntimeLibrary
{
    public sealed class CountdownEvent : IDisposable
    {
        private int mCurrentCount;
        private bool mDisposed;
        private ManualResetEvent mEvent;
        private int mInitialCount;

        /*/////////////////////////////////////////////////////////////////////////////////*/

        public int CurrentCount
        {
            get
            {
                return this.mCurrentCount;
            }
        }

        public int InitialCount
        {
            get
            {
                return this.mInitialCount;
            }
        }

        public bool IsSet
        {
            get
            {
                return (this.mCurrentCount == 0);
            }
        }

        public WaitHandle WaitHandle
        {
            get
            {
                this.ThrowIfDisposed();
                return this.mEvent;
            }
        }

        /*/////////////////////////////////////////////////////////////////////////////////*/

        public CountdownEvent(int initialCount)
        {
            if (initialCount < 0)
                throw new ArgumentOutOfRangeException("initialCount");
            
            this.mInitialCount = initialCount;
            this.mCurrentCount = initialCount;
            this.mEvent = new ManualResetEvent(false);
            
            if (initialCount == 0)
                this.mEvent.Set();
        }

        private void ThrowIfDisposed()
        {
            if (this.mDisposed)
            {
                throw new ObjectDisposedException("CountdownEvent");
            }
        }

        public void Dispose()
        {
            mEvent.Close();
            this.mDisposed = true;
        }

        public void Reset()
        {
            this.Reset(this.mInitialCount);
        }

        public void Reset(int count)
        {
            if (mDisposed)
                throw new ObjectDisposedException("CountdownEvent");

            if (count < 0)
                throw new ArgumentOutOfRangeException("count");

            this.mCurrentCount = count;
            this.mInitialCount = count;
            if (count == 0)
                this.mEvent.Set();
            else
                this.mEvent.Reset();
        }

        /*/////////////////////////////////////////////////////////////////////////////////*/

        public bool Signal()
        {
            return Signal(1);
        }

        public bool Signal(int signalCount)
        {
            if (signalCount <= 0)
                throw new ArgumentOutOfRangeException("signalCount");
            this.ThrowIfDisposed();

            SpinWait wait = new SpinWait();
            int num;
        
            Start:
            num = this.mCurrentCount;

            if (num < signalCount)
                throw new InvalidOperationException("CountdownEvent Decrement BelowZero");
            
            if (Interlocked.CompareExchange(ref this.mCurrentCount, num - signalCount, num) != num)
            {
                wait.SpinOnce();
                goto Start;
            }
            
            if (num == signalCount)
            {
                this.mEvent.Set();
                return true;
            }

            return false;
        }
        

        public void AddCount()
        {
            this.AddCount(1);
        }

        public void AddCount(int signalCount)
        {
            if (!this.TryAddCount(signalCount))
                throw new InvalidOperationException("CountdownEvent Already Zero");
        }

        public bool TryAddCount()
        {
            return this.TryAddCount(1);
        }

        public bool TryAddCount(int signalCount)
        {
            
            if (signalCount <= 0)
                throw new ArgumentOutOfRangeException("signalCount");
            
            this.ThrowIfDisposed();

            SpinWait wait = new SpinWait();
            int num;


            Start:
            num = this.mCurrentCount;
            
            if (num == 0)
                return false;
            
            if (num > (int.MaxValue - signalCount))
                throw new InvalidOperationException("CountdownEvent Already Max");
            
            if (Interlocked.CompareExchange(ref this.mCurrentCount, num + signalCount, num) != num)
            {
                wait.SpinOnce();
                goto Start;
            }

            return true;
        }


        public void Wait()
        {
            this.Wait(-1);
        }

        public bool Wait(TimeSpan timeout)
        {
            long totalMilliseconds = (long)timeout.TotalMilliseconds;
            
            if ((totalMilliseconds < -1L) || (totalMilliseconds > int.MaxValue))
                throw new ArgumentOutOfRangeException("timeout");

            return this.Wait((int)totalMilliseconds);
        }

        public bool Wait(int millisecondsTimeout)
        {
            if (millisecondsTimeout < -1)
                throw new ArgumentOutOfRangeException("millisecondsTimeout");
            
            this.ThrowIfDisposed();
            
            bool isSet = this.IsSet;
            if (!isSet)
                isSet = this.mEvent.WaitOne(millisecondsTimeout);
            
            return isSet;
        }

       



    }
}
